#region License

// Copyright (c) 2008, BXO Solutions AB
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, 
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright 
//       notice, this list of conditions and the following disclaimer in the 
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the BXO Solutions AB nor the names of its 
//       contributors may be used to endorse or promote products derived from 
//       this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Castle.ActiveRecord;

namespace Bxo.TestingPortal.Entities {
    [ActiveRecord]
    public class Scenario {
        private IList<Category> categories;

        private bool isActive;

        private Project project;

        private IList<ScenarioVersion> versions;

        public Scenario(ScenarioVersion initialVersion) {
            if (initialVersion == null) {
                throw new ArgumentNullException("initialVersion");
            }

            this.categories = new List<Category>();
            this.isActive = true;
            this.versions = new List<ScenarioVersion>();
            this.Update(initialVersion);
        }

        /// <summary>
        /// This member is provided only to satisfy NHibernate requirements and should not be used
        /// by user code.
        /// </summary>
        protected Scenario() {
        }

        [HasMany(Access = PropertyAccess.FieldCamelcase, Cascade = ManyRelationCascadeEnum.AllDeleteOrphan, RelationType = RelationType.List, Index = "Position")]
        public virtual IList<ScenarioVersion> Versions {
            get { return new List<ScenarioVersion>(this.versions).AsReadOnly(); }
        }

        /// <summary>
        /// Gets the unique identifier for the scenario.
        /// </summary>
        /// <value>
        /// Identifier for the scenario.
        /// </value>
        [PrimaryKey]
        public virtual int Id { get; set; }

        /// <summary>
        /// Gets the project that this summary belongs to.
        /// </summary>
        /// <value>
        /// Project that this scenario is part of.
        /// </value>
        [BelongsTo(Cascade = CascadeEnum.SaveUpdate)]
        public virtual Project Project {
            get { return this.project; }
            internal set { this.project = value; }
        }

        /// <summary>
        /// Gets the list of categories the scenario belongs to.
        /// </summary>
        /// <value>
        /// List of categories the scenario belongs to.
        /// </value>
        [HasAndBelongsToMany(Access = PropertyAccess.FieldCamelcase, Cascade = ManyRelationCascadeEnum.SaveUpdate, Table = "ScenarioCategoryAssociations", ColumnKey = "Scenario", ColumnRef = "Category")]
        public virtual ICollection<Category> Categories {
            get { return new CategoryList(this.categories, this); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this scenario is still active (not obsoleted
        /// or replaced with other scenario).
        /// </summary>
        /// <value>
        /// <see langword="true"/> if the scenario is still active 
        /// or <see langword="false"/> if it is obsolete or replaced.
        /// </value>
        [Property]
        public virtual bool IsActive {
            get { return this.isActive; }
            set { this.isActive = value; }
        }

        public virtual ScenarioVersion CurrentVersion {
            get { return this.versions[this.versions.Count - 1]; }
        }

        public override string ToString() {
            return this.CurrentVersion.Summary;
        }

        public virtual void Update(ScenarioVersion newVersion) {
            if (newVersion == null) {
                throw new ArgumentNullException("newVersion");
            }

            if (newVersion.Scenario != null) {
                throw new ArgumentException("Specified version already belongs to a scenario.");
            }

            newVersion.Scenario = this;
            this.versions.Add(newVersion);
        }

        #region Nested type: CategoryList

        private class CategoryList : Collection<Category> {
            private readonly Scenario owner;

            public CategoryList(IList<Category> innerList, Scenario owner) : base(innerList) {
                this.owner = owner;
            }

            protected override void ClearItems() {
                Category[] copy = this.ToArray();

                base.ClearItems();

                Array.ForEach(copy, category => this.OnRemoved(category));
            }

            protected override void InsertItem(int index, Category item) {
                if (this.owner.Project == null) {
                    throw new InvalidOperationException("Cannot add category to scenario which has no project.");
                }

                this.OnValidate(item);

                if (this.Contains(item)) {
                    throw new InvalidOperationException("Category is already added.");
                }

                base.InsertItem(index, item);

                this.OnAdded(item);
            }

            protected override void RemoveItem(int index) {
                Category item = this[index];

                base.RemoveItem(index);

                this.OnRemoved(item);
            }

            protected override void SetItem(int index, Category item) {
                throw new NotSupportedException();
            }

            protected virtual void OnValidate(Category item) {
                // The category must belong to the same project as the scenario.
                // Check the membership in the collection to prevent issues with reference comparisons
                // of persisted objects.
                if (! this.owner.Project.Categories.Contains(item)) {
                    throw new ArgumentException("Cannot add category that does not belong to the same project as the scenario.");
                }
            }

            protected virtual void OnAdded(Category item) {
                // Check if the item is already added is needed to prevent infinite loop.
                if (!item.Scenarios.Contains(this.owner)) {
                    item.Scenarios.Add(this.owner);
                }
            }

            protected virtual void OnRemoved(Category item) {
                item.Scenarios.Remove(this.owner);
            }
        }

        #endregion
    }
}